#pragma once

#include <sys/time.h>

#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
#include <atomic>
#include <optional>
#include <memory>
#include <string>
#include <condition_variable>

#include <opencv2/opencv.hpp>

/**
 * @brief 借用wmj::now()函数
 * @return 当前时间戳
 */
double now();

/**
 * @class WaitableQueue
 * @brief 实现线程可等待队列的类
 */
template <typename TDataType>
class WaitableQueue
{
   private:
    struct node
    {
        std::shared_ptr<TDataType> data;
        std::atomic<node*> next;

        node() : next(nullptr) {}
        explicit node(TDataType &&value)
            : data(std::make_shared<TDataType>(std::move(value))), next(nullptr)
        {
        }
        explicit node(const TDataType &value)
            : data(std::make_shared<TDataType>(value)), next(nullptr)
        {
        }
    };

    std::atomic<node*> m_head;
    std::atomic<node*> m_tail;
    std::mutex m_mutex;
    std::condition_variable m_condition_variable;
    std::atomic<bool> m_stop{false};
    std::atomic<size_t> m_length;

   public:
    /**
     * @brief 构造函数
     */
    WaitableQueue();

    /**
     * @brief 析构函数
     */
    ~WaitableQueue();

    /**
     * @brief 将一个常值引用构造成一个节点指针推入队列中
     * @param value 准备推入队列的值
     */
    void push(const TDataType &value);

    /**
     * @brief 将一个右值构造成一个节点指针推入队列中
     * @param value 准备推入队列的值
     */
    void push(TDataType &&value);
    
    /**
     * @brief 队列无等待弹出队首
     * @return std::optional的一个值，是一个可能有可能为std::nullopt(也就是没有东西)的一个值
     */
    std::optional<TDataType> pop();

    /**
     * @brief 队列等待condition_variable变量的值弹出队首
     * @return std::optional的一个值，是一个可能有可能为std::nullopt(也就是没有东西)的一个值
     */
    std::optional<TDataType> wait_and_pop();

    /**
     * @brief 设置队列线程停止,并通知其他线程
     */
    void set_stop();

    /**
     * @brief 返回当前队列是否为空
     * @return 一个bool类型的值，true为空，false为非空 
     */
    bool empty() const;

    /**
     * @brief 返回当前队列剩余元素的数量
     * @return 一个size_t类型的值，表示元素数量
     */
    size_t size() const;
};

/**
 * @class AsyncVideoWriter
 * @brief 借助上面的队列写的一个线程异步的VideoWriter类
 */
class AsyncVideoWriter
{
   private:
    WaitableQueue<cv::Mat> m_frame_queue;
    std::thread m_writer_thread;
    std::atomic<bool> m_is_running{false};
    std::atomic<bool> m_should_stop{false};

    cv::VideoWriter m_video_writer;
    cv::Size m_image_size;
    double m_fps;
    std::string m_file_name;
    int m_fourcc;

   public:
   /**
    * @brief 不完全初始化的构造函数
    */
    AsyncVideoWriter();

    /**
     * @brief 完全初始化的构造函数，几乎同cv::VideoWriter
     * @param filename 准备写入视频的地址
     * @param init_fourcc 准备写入视频的fourcc格式，默认为MJPG
     * @param fps 准备写入视频的帧率，默认为30
     * @param frame_size 准备写入视频的图像的大小，默认为1280x1024
     */
    AsyncVideoWriter(const std::string &filename,
                     int init_fourcc,
                     double fps,
                     cv::Size frame_size);
    
    /**
     * @brief 析构函数，等待write线程完成
     */
    ~AsyncVideoWriter();

    /**
     * @brief 对于先前不完全初始化的writer使用open函数来进行初始化并启动录制，几乎同cv::VideoWriter
     * @param filename 准备写入视频的地址
     * @param init_fourcc 准备写入视频的fourcc格式，默认为MJPG
     * @param fps 准备写入视频的帧率，默认为30
     * @param frame_size 准备写入视频的图像的大小，默认为1280x1024
     */
    void open(const std::string &filename,
              int init_fourcc,
              double fps, cv::Size frame_size);

    /**
     * @brief 类似于cv::VideoWriter的write函数，并在两帧时间差不足1/fps的时候执行跳帧操作
     * @param frame 视频帧，这里是常值引用
     */
    void write(const cv::Mat &frame);

    /**
     * @brief 类似于cv::VideoWriter的write函数，并在两帧时间差不足1/fps的时候执行跳帧操作
     * @param frame 视频帧，这里是右值引用
     */
    void write(cv::Mat &&frame);

    /**
     * @brief 类似于cv::VideoWriter重载的operator<<，并在两帧时间差不足1/fps的时候执行跳帧操作
     * @param frame 视频帧，这里是常值引用
     */
    void operator<<(const cv::Mat &frame);

    /**
     * @brief 类似于cv::VideoWriter重载的operator<<，并在两帧时间差不足1/fps的时候执行跳帧操作
     * @param frame 视频帧，这里是右值引用
     */
    void operator<<(cv::Mat &&frame);

    /**
     * @brief 将VideoWriter的所有拿图操作停止，并等待队列中剩余图像清理完成
     */
    void release();

    /**
     * @brief 拿private中WaitableQueue对象的size()
     * @return 一个size_t类型的量，表示当前队列剩余元素数量
     */
    size_t pendingFrames() const;

};